﻿/// <reference path="../Libraries/jasmine/jasmine.d.ts" />
/// <reference path="../ClassManager.ts" />

describe('ExtSpec.ClassManager', function () {
    'use strict';

    var manager = ExtSpec.ClassManager;

    describe('set', function () {
        it('should throw given undefined class name', function () {
            expect(function () {
                manager.set(undefined, {});
            }).toThrowError('Class name argument must be a string.');
        });
    });

    describe('get', function () {
        it('should throw given no arguments', function () {
            expect(function () {
                manager.get(undefined);
            }).toThrowError('The class named "undefined" could not be found.');
        });

        it('should throw given an unknown class name', function () {
            expect(function () {
                manager.get('Some.unknown.Class');
            }).toThrowError('The class named "Some.unknown.Class" could not be found.');
        });

        it('should retrieve a named class definition', function () {
            var definition = {};

            manager.set('App.namespace.Class', definition);

            expect(manager.get('App.namespace.Class')).toBe(definition);
        });
    });

    describe('construct', function () {
        var getSpy: jasmine.Spy;

        beforeEach(function () {
            getSpy = spyOn(manager, 'get');
            spyOn(ExtSpec, 'apply');
        });

        it('should call get, passing through the class name argument', function () {
            getSpy.and.returnValue({});

            manager.construct('Class.to.Construct');

            expect(getSpy).toHaveBeenCalledWith('Class.to.Construct');
        });

        it('should throw if get does not return an object', function () {
            getSpy.and.returnValue('not an object');

            expect(function () {
                manager.construct('Not.a.Class');
            }).toThrowError('The definition for "Not.a.Class" must be an object type.');
        });

        it('should return a constructor when get returns an object', function () {
            getSpy.and.returnValue({
                someProperty: true
            });

            var actual = manager.construct('Not.a.Class');

            expect(typeof actual).toBe('function');
        });

        it('should return a constructor when get returns a function', function () {
            getSpy.and.returnValue(function () {
                return {
                    someProperty: true
                };
            });

            var actual = manager.construct('Not.a.Class');

            expect(typeof actual).toBe('function');
        });

        it('should invoke with the constructor when get returns a function', function () {
            var actual,
                functionForm = jasmine.createSpy('functionForm').and.returnValue({
                    someProperty: true
                }),
                getter: any = manager.get,
                getterSpy: jasmine.Spy = getter;

            getter.and.returnValue(functionForm);

            actual = manager.construct('Not.a.Class');

            expect(functionForm).toHaveBeenCalledWith(jasmine.any(Function));
        });

        it('should throw when get returns a function used with override', function () {
            getSpy.and.returnValue(function () {
                return {
                    override: 'Some.Class'
                };
            });

            expect(function () {
                manager.construct('Not.a.Class');
            }).toThrowError('Function form cannot be used for overrides (EXTJSIV-7805).');
        });

        it('should call apply on statics if they exist', function () {
            var actual,
                definition = {
                    statics: {
                        doSomething: function () { }
                    },
                    inheritableStatics: {}
                };

            getSpy.and.returnValue(definition);

            actual = manager.construct('Not.a.Class');

            expect(ExtSpec.apply).toHaveBeenCalledWith(actual, definition.statics);
            expect(ExtSpec.apply).toHaveBeenCalledWith(actual, definition.inheritableStatics);
        });

        it('should call apply on non statics', function () {
            var actual,
                definition = {
                    aProperty: 99.99,
                    aMethod: function () { }
                };

            getSpy.and.returnValue(definition);

            actual = manager.construct('Not.a.Class');

            expect(ExtSpec.apply).toHaveBeenCalledWith(actual.prototype, definition, ['statics', 'inheritableStatics']);
        });
    });

    describe('create', function () {
        var ctor = function () { };

        beforeEach(function () {
            spyOn(manager, 'construct').and.returnValue(ctor);
        });

        it('should be aliased to ExtSpec.create', function () {
            expect(ExtSpec.create).toBe(manager.create);
        });

        it('should call construct, passing through the class name argument', function () {
            manager.create('Not.a.Class');

            expect(manager.construct).toHaveBeenCalledWith('Not.a.Class', undefined);
        });

        it('should return a new instance of the constructor returned from create', function () {
            var actual = manager.create('Not.a.Class');

            expect(actual).toEqual(jasmine.any(ctor));
        });
    });

    describe('callback', function () {
        it('should throw given no arguments', function () {
            expect(function () {
                manager.callback(undefined);
            }).toThrowError('The class named "undefined" was not defined with a callback.');
        });

        it('should throw given an unknown class name', function () {
            expect(function () {
                manager.callback('Some.unknown.Class');
            }).toThrowError('The class named "Some.unknown.Class" was not defined with a callback.');
        });

        it('should retrieve a callback for a class', function () {
            var callback = function () { };

            manager.set('App.namespace.Class', {}, callback);

            expect(manager.callback('App.namespace.Class')).toBe(callback);
        });
    });
});

describe('Ext', function () {
    'use strict';

    describe('define', function () {
        beforeEach(function () {
            spyOn(ExtSpec, 'warn');
            spyOn(ExtSpec.ClassManager, 'set');
        });

        it('should be an ES5 accessor', function () {
            var descriptor = Object.getOwnPropertyDescriptor(Ext, 'define');

            expect(descriptor.get).toBeDefined();
            expect(descriptor.set).toBeDefined();
        });

        it('should call ExtSpec.ClassManager.set', function () {
            Ext.define('something', {});

            expect(ExtSpec.ClassManager.set).toHaveBeenCalledWith('something', {}, undefined);
        });

        it('should call both ExtSpec.ClassManager.set and the real Ext.define if it exists', function () {
            var define = jasmine.createSpy('define');
            Ext.define = define;

            Ext.define('something', {});

            expect(ExtSpec.ClassManager.set).toHaveBeenCalledWith('something', {}, undefined);
            expect(define).toHaveBeenCalledWith('something', {}, undefined);

            Ext.define = undefined;
        });

        it('should warn if define is set', function () {
            Ext.define = null;

            expect(ExtSpec.warn).toHaveBeenCalled();
        });
    });
});